/*
 * Copyright Groupe Adeo 2009
 * 
 * Licensed under New BSD License : http://www.opensource.org/licenses/bsd-license.php
 */
package com.adeoservices.gwt.dispatch.spring.server;

import java.util.List;

import net.customware.gwt.dispatch.server.ActionHandler;
import net.customware.gwt.dispatch.server.ActionHandlerRegistry;
import net.customware.gwt.dispatch.server.ActionResult;
import net.customware.gwt.dispatch.server.Dispatch;
import net.customware.gwt.dispatch.server.ExecutionContext;
import net.customware.gwt.dispatch.shared.Action;
import net.customware.gwt.dispatch.shared.ActionException;
import net.customware.gwt.dispatch.shared.Result;
import net.customware.gwt.dispatch.shared.UnsupportedActionException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.stereotype.Component;

import com.adeoservices.gwt.dispatch.spring.server.security.Permission;
import com.adeoservices.gwt.dispatch.spring.server.security.PermissionVerifier;
import com.adeoservices.gwt.dispatch.spring.server.security.PermissionVerifierRegistry;

/**
 * 
 * @author Olivier HEDIN / Adeo Services
 * 
 */
@Component
public class SpringDispatchImpl implements Dispatch {

	private final Log logger = LogFactory.getLog(getClass());

	private static class DefaultExecutionContext implements ExecutionContext {
		private final SpringDispatchImpl dispatch;

		private final List<ActionResult<?, ?>> actionResults;

		private DefaultExecutionContext(SpringDispatchImpl dispatch) {
			this.dispatch = dispatch;
			this.actionResults = new java.util.ArrayList<ActionResult<?, ?>>();
		}

		public <A extends Action<R>, R extends Result> R execute(A action)
				throws ActionException {
			return execute(action, true);
		}

		public <A extends Action<R>, R extends Result> R execute(A action,
				boolean allowRollback) throws ActionException {
			R result = dispatch.doExecute(action, this);
			if (allowRollback)
				actionResults.add(new ActionResult<A, R>(action, result));
			return result;
		}

		/**
		 * Rolls back all logged action/results.
		 * 
		 * @throws ActionException
		 *             If there is an action exception while rolling back.
		 * @throws ServiceException
		 *             If there is a low level problem while rolling back.
		 */
		private void rollback() throws ActionException {
			for (int i = actionResults.size() - 1; i >= 0; i--) {
				ActionResult<?, ?> actionResult = actionResults.get(i);
				rollback(actionResult);
			}
		}

		private <A extends Action<R>, R extends Result> void rollback(
				ActionResult<A, R> actionResult) throws ActionException {
			dispatch.doRollback(actionResult.getAction(), actionResult
					.getResult(), this);
		}

	};

	private final ActionHandlerRegistry handlerRegistry;
	private final PermissionVerifierRegistry permissionVerifierRegistry;

	@Autowired
	public SpringDispatchImpl(ActionHandlerRegistry handlerRegistry,
			PermissionVerifierRegistry permissionVerifierRegistry) {
		this.handlerRegistry = handlerRegistry;
		this.permissionVerifierRegistry = permissionVerifierRegistry;
	}

	public <A extends Action<R>, R extends Result> R execute(A action)
			throws ActionException {
		DefaultExecutionContext ctx = new DefaultExecutionContext(this);
		try {
			return doExecute(action, ctx);
		} catch (ActionException e) {
			ctx.rollback();
			throw e;
		} catch (AccessDeniedException e)
		{
			logger.warn("Access denied");
			throw new ActionException("Access denied");
		}
	}

	private <A extends Action<R>, R extends Result> R doExecute(A action,
			ExecutionContext ctx) throws ActionException {
		ActionHandler<A, R> handler = findHandler(action);
		return handler.execute(action, ctx);
	}

	private <A extends Action<R>, R extends Result> ActionHandler<A, R> findHandler(
			A action) throws UnsupportedActionException, ActionException {

		ActionHandler<A, R> handler = handlerRegistry.findHandler(action);

		if (handler == null)
			throw new UnsupportedActionException(action);

		PermissionVerifier<A, R> permissionVerifier = findPermissionVerifier(handler);

		if (permissionVerifier != null && !permissionVerifier.verifyPermission(action)) {
			logger.info("Access denied for action : " + action + " by verifier : " + permissionVerifier);
			throw new AccessDeniedException("Access denied");
		}

		return handler;
	}

	@SuppressWarnings("unchecked")
	private <A extends Action<R>, R extends Result> PermissionVerifier<A, R> findPermissionVerifier(
			ActionHandler<A, R> actionHandler) {
		PermissionVerifier<A, R> permissionVerifier = null;

		Permission permissionAnnotation = actionHandler.getClass()
				.getAnnotation(Permission.class);

		if (permissionAnnotation != null) {
			permissionVerifier = (PermissionVerifier<A, R>) findPermissionVerifierInstance(permissionAnnotation);
		}

		return permissionVerifier;
	}

	private PermissionVerifier<?, ?> findPermissionVerifierInstance(
			Permission permissionAnnotation) {

		return permissionVerifierRegistry
				.findPermissionVerifier(permissionAnnotation.verifier());

	}

	private <A extends Action<R>, R extends Result> void doRollback(A action,
			R result, ExecutionContext ctx) throws ActionException {
		ActionHandler<A, R> handler = findHandler(action);
		handler.rollback(action, result, ctx);
	}
}
